/*
 * Copyright (C) 2011, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include "VCModSpeexAEC.h"

#if VC_PLATFORM_WIN
#pragma comment(lib, "libspeexdsp.lib")
#endif

VCModSpeexAECBack::VCModSpeexAECBack(VCIController & controller, VCModSpeexAEC & forward)
: VCIMod(controller)
, m_forward(forward)
{

}

VCModSpeexAECBack::~VCModSpeexAECBack()
{

}

VC_RESULT VCModSpeexAECBack::Push(VC_CMD cmd, void * data_buff, size_t data_size, VC_DATAFORMAT data_format)
{
	VC_RESULT result = VC_FALSE;
	if (data_format == VC_PCM_1_32000_S16)
	{
		switch(cmd)
		{
		case VC_STREAM_BEGIN:
			{
				VCAutoMutex lock(m_forward.m_buff_back_mutex);
				m_forward.m_buff_back.Clear();
				m_forward.m_buff_back.Push(data_buff, data_size);

				if (m_forward.m_buff_back.Size() > VCMODSPEEXAEC_WAIT_SIZE)
				{
					m_forward.m_buff_back.PopSize(m_forward.m_buff_back.Size() - VCMODSPEEXAEC_WAIT_SIZE);
				}
				result = VC_OK;
			}
			break;
		case VC_STREAM:
			{
				VCAutoMutex lock(m_forward.m_buff_back_mutex);
				m_forward.m_buff_back.Push(data_buff, data_size);

				if (m_forward.m_buff_back.Size() > VCMODSPEEXAEC_WAIT_SIZE)
				{
					m_forward.m_buff_back.PopSize(m_forward.m_buff_back.Size() - VCMODSPEEXAEC_WAIT_SIZE);
				}
				result = VC_OK;
			}
			break;
		case VC_STREAM_END:
			{
				VCAutoMutex lock(m_forward.m_buff_back_mutex);
				m_forward.m_buff_back.Push(data_buff, data_size);

				if (m_forward.m_buff_back.Size() > VCMODSPEEXAEC_WAIT_SIZE)
				{
					m_forward.m_buff_back.PopSize(m_forward.m_buff_back.Size() - VCMODSPEEXAEC_WAIT_SIZE);
				}
				result = VC_OK;
			}
			break;
		default:
			break;
		}
		if (m_reciver)
		{
			m_reciver->Push(cmd, data_buff, data_size, data_format);
		}
	} else
	{
		if (m_reciver)
		{
			result = m_reciver->Push(cmd, data_buff, data_size, data_format);
		}
	}
	return result;
}

VCModSpeexAEC::VCModSpeexAEC(VCIController & controller)
: VCIMod(controller)
, m_back(NULL)
, m_echo_state(NULL)
, m_framesize(640/*20ms*/ * sizeof(short))
{
	m_echo_state = speex_echo_state_init(m_framesize / sizeof(short), 3200/*100ms(speeker->mic)*/ + 0/*output module*/);
	m_back = new VCModSpeexAECBack(controller, *this);
	if (m_echo_state && m_back)
	{
		TRACE("aec init ok")
		controller.OnNotifyFromMod(*this, VC_INFO_INIT);
	} else
	{
		TRACE("aec init false")
		controller.OnNotifyFromMod(*this, VC_ERROR_INIT);
	}
}

VCModSpeexAEC::~VCModSpeexAEC()
{
	if (m_back)
	{
		delete m_back;
	}
	if (m_echo_state)
	{
		speex_echo_state_destroy(m_echo_state);
	}
}

VC_RESULT VCModSpeexAEC::Push(VC_CMD cmd, void * data_buff, size_t data_size, VC_DATAFORMAT data_format)
{
	VC_RESULT result = VC_FALSE;
	if (data_format == VC_PCM_1_32000_S16)
	{
		switch(cmd)
		{
		case VC_STREAM_BEGIN:
			{
				m_buff_in.Clear();
				m_buff_out.Clear();
				if (m_reciver)
				{
					m_reciver->Push(VC_STREAM_BEGIN, NULL, 0, VC_PCM_1_32000_S16);
				}
				
				if (m_echo_state)
				{
					speex_echo_state_reset(m_echo_state);
				}

				m_buff_in.Push(data_buff, data_size);
				m_buff_out.PushZero(m_framesize);
				
				while (m_buff_in.Size() >= (size_t)m_framesize)
				{
					{
						VCAutoMutex lock(m_buff_back_mutex);
						if (m_echo_state && (m_buff_back.Size() >= (size_t)m_framesize))
						{
							speex_echo_playback(m_echo_state, (const spx_int16_t *)m_buff_back.GetDataPtr());
							speex_echo_capture(m_echo_state, (const spx_int16_t *)m_buff_in.GetDataPtr(), (spx_int16_t *)m_buff_out.GetDataPtr());
							m_buff_back.PopSize(m_framesize);
						} else
						{
							memcpy(m_buff_out.GetDataPtr(), m_buff_in.GetDataPtr(), m_framesize);
						}
					}
					if (m_reciver)
					{
						m_reciver->Push(VC_STREAM, m_buff_out.GetDataPtr(), m_framesize, VC_PCM_1_32000_S16);
					}
					m_buff_in.PopSize(m_framesize);
				}
			}
			break;
		case VC_STREAM:
			{
				m_buff_in.Push(data_buff, data_size);
				
				while (m_buff_in.Size() >= (size_t)m_framesize)
				{
					{
						VCAutoMutex lock(m_buff_back_mutex);
						if (m_echo_state && (m_buff_back.Size() >= (size_t)m_framesize))
						{
							speex_echo_playback(m_echo_state, (const spx_int16_t *)m_buff_back.GetDataPtr());
							speex_echo_capture(m_echo_state, (const spx_int16_t *)m_buff_in.GetDataPtr(), (spx_int16_t *)m_buff_out.GetDataPtr());
							m_buff_back.PopSize(m_framesize);
						} else
						{
							memcpy(m_buff_out.GetDataPtr(), m_buff_in.GetDataPtr(), m_framesize);
						}
					}
					if (m_reciver)
					{
						m_reciver->Push(VC_STREAM, m_buff_out.GetDataPtr(), m_framesize, VC_PCM_1_32000_S16);
					}
					m_buff_in.PopSize(m_framesize);
				}
			}
			break;
		case VC_STREAM_END:
			if (m_echo_state)
			{
				m_buff_in.Push(data_buff, data_size);
				
				while (m_buff_in.Size() >= (size_t)m_framesize)
				{
					{
						VCAutoMutex lock(m_buff_back_mutex);
						if (m_echo_state && (m_buff_back.Size() >= (size_t)m_framesize))
						{
							speex_echo_playback(m_echo_state, (const spx_int16_t *)m_buff_back.GetDataPtr());
							speex_echo_capture(m_echo_state, (const spx_int16_t *)m_buff_in.GetDataPtr(), (spx_int16_t *)m_buff_out.GetDataPtr());
							m_buff_back.PopSize(m_framesize);
						} else
						{
							memcpy(m_buff_out.GetDataPtr(), m_buff_in.GetDataPtr(), m_framesize);
						}
					}
					if (m_reciver)
					{
						m_reciver->Push(VC_STREAM, m_buff_out.GetDataPtr(), m_framesize, VC_PCM_1_32000_S16);
					}
					m_buff_in.PopSize(m_framesize);
				}

				if (m_reciver)
				{
					m_reciver->Push(VC_STREAM_END, NULL, 0, VC_PCM_1_32000_S16);
				}
			}
			break;
		default:
			break;
		}
	} else
	{
		if (m_reciver)
		{
			result = m_reciver->Push(cmd, data_buff, data_size, data_format);
		}
	}
	return result;
}

